/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"

char *_create_dirname(char *progname);
char *_init_common(char *progname);

// sets global outputlog_dirname,outputlog_active
// if you want to use outputlog, set active=true, better here than in client code
// creates it and symlink to it
// progname must be non-NULL string
bool outputlog_init(bool active, char *progname)
{
    pid_t pid = getpid();

    size_t i;
    char fixed[64];

    outputlog_active = active;
    if (!outputlog_active)
      {
	  outputlog_dirname = NULL;	// to be safe and do *something* defined since it should be set 
	  return true;		// successful, even though active=false.
      }

    strncpy(fixed, progname, sizeof(fixed));

    for (i = 0; i < strlen(fixed); i++)
	if (!isalnum(fixed[i]))
	    fixed[i] = '_';

    char template[128];
    snprintf(template, sizeof(template), "OMFoutputlog.%d.%s.XXXXXX", pid,
	     fixed);
    /* NOTE: strdup so that it is on heap and not template on stack. */

    outputlog_dirname = mkdtemp(checked_strdup(template));
    if (outputlog_dirname == NULL)
      {
	  perror("mkdtemp()");
	  return false;
      }
    debug1_printf("created outputlog directory: %s\n", outputlog_dirname);
    //TODO: check if existing output-latest is a symlink. so u dont
    //delete someone's file! this is dangerous unlinkbelow..//
    snprintf(template, sizeof(template), "OMFoutputlog-latest");
    unlink(template);		// remove if possible. no need to error check this
    symlink(outputlog_dirname, template);	//todo: error check this
    debug1_printf("created symlink: %s => %s\n", template,
		  outputlog_dirname);

    return true;
}

//arg_val is assumed to be \0 c string. in futur will also passleng to support binary patterns.
// argslist is llist of type struct st_dlarg
bool outputlog_dl_chldlog_init(LLIST_P argslist, const char *symbol)
{
    FILE *fp;
    char fpath[1024];
    char *dirname;
    int i;
    LLIST_P walker;
    DL_ARG_P dlarg;

    if (!outputlog_active)
      {
	  fp = fopen("/dev/null", "a+");
	  if (fp == NULL)
	    {
		perror("/dev/null");
		return false;
	    }
	  //error check this
	  if (dup2(fileno(fp), fileno(stdout)) < 0
	      || dup2(fileno(fp), fileno(stderr)) < 0)
	    {
		perror("dup2()");
		fclose(fp);
		return false;
	    }

	  return true;
      }

    dirname = _init_common((char *) symbol);	// use symbol (funcname) as progname.
    if (dirname == NULL)
	return false;

    snprintf(fpath, sizeof(fpath), "%s/funcargs.log", dirname);
    fp = fopen(fpath, "a+");
    if (fp == NULL)
      {
	  perror(fpath);
	  free(dirname);
	  return false;
      }

    for (i = 0, walker = argslist; walker != NULL;
	 walker = walker->next, i++)
      {
	  dlarg = walker->element;
	  fprintf(fp, "arg[%d]: type=%s, val=", i,
		  dlarg_type_to_str(dlarg->type));
	  switch (dlarg->type)
	    {
	    case TYPE_CSTRING_PTR:
		fprintf(fp, "<%p> [ %s ]\n", dlarg->val.string_val,
			dlarg->val.string_val);
		break;
	    case TYPE_GENERIC_PTR:
		fprintf(fp, "<%p>\n", dlarg->val.ptr_val);	//TODO: this will be replaced to support binary fuzzing. 
		break;
	    case TYPE_INT:
		fprintf(fp, "%d\n", dlarg->val.int_val);
		break;
	    case TYPE_FLOAT:
		fprintf(fp, "%f\n", dlarg->val.float_val);
		break;
	    default:
		break;
	    }
      }
    fclose(fp);
    free(dirname);

    return true;
}

//intended to be called from the child process before it calls exec()
//creates subdirectory for child process and dup2's stdout to stdout.log
// dup2's stderr to stderr.log
// if stdin_ar is not NULL creates stdin.log of written data
bool outputlog_command_chldlog_init(char *progname, uint8_t * stdin_ar,
				    size_t stdin_len, int argc,
				    uint8_t * arg_val[])
{
    if (!outputlog_active)
      {
	  FILE *fp;
	  if ((fp = fopen("/dev/null", "a+")) == NULL)
	    {
		perror("/dev/null");
		return false;
	    }
	  //error check this
	  dup2(fileno(fp), fileno(stdout));
	  dup2(fileno(fp), fileno(stderr));


	  return true;
      }

    char fpath[1024];
    FILE *fp;
    char *dirname = _init_common(progname);
    if (dirname == NULL)
	return false;

//only target stdin $$; will have this. 
    if (stdin_ar != NULL)
      {
	  snprintf(fpath, sizeof(fpath), "%s/stdin.log", dirname);
	  if ((fp = fopen(fpath, "a+")) == NULL)
	    {
		perror(fpath);
		free(dirname);
		return false;
	    }
	  //error check this
	  //None of this is working to duplicate the stdin to file, so since we have pointer to stdin data (since fork() copies parent a/s we can just write it all at once 
	  //TODO: for now later will need to do this differently.
	  fwrite(stdin_ar, sizeof(uint8_t), stdin_len, fp);	//TODO:error check
	  fclose(fp);		// don't need stdin.log anymore, FORNOW.
      }

    snprintf(fpath, sizeof(fpath), "%s/argv.log", dirname);
    if ((fp = fopen(fpath, "a+")) == NULL)
      {
	  perror(fpath);
	  free(dirname);
	  return false;
      }
    int i;
    for (i = 0; i < argc; i++)
      {
	  fprintf(fp, "argv[%d]: %s\n", i, arg_val[i]);	//TODO: this will be replaced to support binary fuzzing. 
      }
    fclose(fp);


    free(dirname);
    return true;
}

//returns dirname, must be freed
char *_init_common(char *progname)
{
    char *dirname = _create_dirname(progname);
    char fpath[1024];
    FILE *fp;
    snprintf(fpath, sizeof(fpath), "%s/stdout.log", dirname);
    if ((fp = fopen(fpath, "a+")) == NULL)
      {
	  perror(fpath);
	  free(dirname);
	  return NULL;
      }
    //error check this
    dup2(fileno(fp), fileno(stdout));

    snprintf(fpath, sizeof(fpath), "%s/stderr.log", dirname);
    if ((fp = fopen(fpath, "a+")) == NULL)
      {
	  perror(fpath);
	  free(dirname);
	  return NULL;
      }
    //error check this
    dup2(fileno(fp), fileno(stderr));

    return dirname;
}

//dir is the dir to put it in, resolved in sigchld hander from subdirs.txt
//creates file termination.log with contents msg
bool outputlog_create_termination_log(char *dir, char *msg)
{
    if (!outputlog_active)
	return true;



    char fpath[1024];
    FILE *fp;

    snprintf(fpath, sizeof(fpath), "%s/termination.log", dir);
    if ((fp = fopen(fpath, "a+")) == NULL)
      {
	  perror(fpath);
	  return false;
      }
    fprintf(fp, "%s", msg);
    fclose(fp);
    return true;
}

//creates new dir, adds to subdirs.txt
//returns NULL on error
//need to free return address.
// progname must be non-NULL string
char *_create_dirname(char *progname)
{
    pid_t pid = getpid();
    size_t i;
    char *dirname;
    char template[128];
    char fixed[64];
    char c0, c1;
    FILE *fp;

    strncpy(fixed, progname, sizeof(fixed));

    for (i = 0; i < strlen(fixed); i++)
	if (!isalnum(fixed[i]))
	    fixed[i] = '_';


    // (65,90) ~ (A,Z)
    c0 = ((unsigned int) pid % ('Z' - 'A')) + 'A';
    c1 = ((unsigned int) (pid + time(NULL)) % ('Z' - 'A')) + 'A';
//    printf("%d[%c] %d[%c]\n",c0,c0,c1,c1);

    snprintf(template, sizeof(template), "%s/%c%c", outputlog_dirname, c0,
	     c1);
    mkdir(template, 0700);	// TODO: only make if it doesn't exist already.

    snprintf(template, sizeof(template), "%s/%c%c/%d.%s.XXXXXX",
	     outputlog_dirname, c0, c1, pid, fixed);

    /* NOTE: strdup so that it is on heap and not template on stack. */
    dirname = mkdtemp(checked_strdup(template));
    if (dirname == NULL)
      {
	  perror("mkdtemp()");
	  return NULL;
      }
    debug1_printf("created directory: %s\n", dirname);

    snprintf(template, sizeof(template), "%s/subdirs.txt",
	     outputlog_dirname);
    fp = fopen(template, "a+");
    if (fp == NULL)
      {
	  perror(template);
	  return NULL;
      }

    if (flock(fileno(fp), LOCK_EX) < 0)
      {
	  perror("flock()");
	  return NULL;
      }
    fprintf(fp, "%d %s\n", pid, dirname);
    fflush(fp);
    if (flock(fileno(fp), LOCK_UN) < 0)
      {

	  perror("flock()");
	  return NULL;
      }
    fclose(fp);

    debug1_printf("resolved cpid %d to directory %s\n", pid, dirname);
    return dirname;
}


//lookup dir name in subdirs.txt
///must free return result, or returns NULL on failure

char *outputlog_get_dirname(pid_t pid)
{
    if (!outputlog_active)
	return NULL;		//where used in main.c is expecting this.

    FILE *fp;
    char buf[4096];
    pid_t pidr;

    snprintf(buf, sizeof(buf), "%s/subdirs.txt", outputlog_dirname);
    if ((fp = fopen(buf, "r")) == NULL)
      {
	  perror(buf);
	  return NULL;
      }
    if (flock(fileno(fp), LOCK_EX) < 0)
      {
	  perror("flock()");
	  fclose(fp);
	  return NULL;
      }

    char *dname = NULL;
    while (!feof(fp))
      {
	  fscanf(fp, "%d %s\n", &pidr, buf);
	  if (pidr == pid)
	    {
		dname = buf;	// don't strdup() it yet, there may be another newer line with same recycled pid 
		//continue loop because we want the last ret value
	    }
      }
//at eof..

    if (flock(fileno(fp), LOCK_UN) < 0)
      {

	  perror("flock()");
	  fclose(fp);
	  return NULL;
      }

    fclose(fp);
    if (dname == NULL)
	return NULL;

    return checked_strdup(dname);	// copy string onto heap
}
